package validatorx

import (
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/go-playground/validator/v10"
)

var (
	splitParamsRegexString = `'[^']*'|\S+`
	splitParamsRegex       = lazyRegexCompile(splitParamsRegexString)
	arrofValsCache         = map[string]map[string]struct{}{}
	arrofValsCacheRWLock   = sync.RWMutex{}
)

func parseArrOfParam(s string) map[string]struct{} {
	arrofValsCacheRWLock.RLock()
	vals, ok := arrofValsCache[s]
	arrofValsCacheRWLock.RUnlock()
	if !ok {
		arrofValsCacheRWLock.Lock()
		vals = map[string]struct{}{}
		results := splitParamsRegex().FindAllString(s, -1)
		for _, result := range results {
			key := strings.ReplaceAll(result, "'", "")
			vals[key] = struct{}{}
		}
		arrofValsCache[s] = vals
		arrofValsCacheRWLock.Unlock()
	}
	return vals
}

func lazyRegexCompile(str string) func() *regexp.Regexp {
	var regex *regexp.Regexp
	var once sync.Once
	return func() *regexp.Regexp {
		once.Do(func() {
			regex = regexp.MustCompile(str)
		})
		return regex
	}
}

func ArrOf(fl validator.FieldLevel) bool {
	vals := parseArrOfParam(fl.Param())
	field := fl.Field()
	if field.Kind() == reflect.Slice {
		for i := 0; i < field.Len(); i++ {
			var v string
			fieldValue := field.Index(i)

			switch fieldValue.Kind() {
			case reflect.String:
				v = fieldValue.String()
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				v = strconv.FormatInt(fieldValue.Int(), 10)
			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
				v = strconv.FormatUint(fieldValue.Uint(), 10)
			default:
				panic(fmt.Sprintf("ArrOf element not support type %s", fieldValue.Type()))
			}

			if _, ok := vals[v]; !ok {
				return false
			}
		}
		return true
	} else {
		panic(fmt.Sprintf("ArrOf only use Slice, Bad field type %s", field.Type()))
	}
}
